﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;

using Bling.Core;
using Bling.WPF;
using Bling.WPF.Util;
using Bling.Slides;
using Bling.Util;
using Bling.Shaders;
using Bling.Graphics;
namespace Bling.Example {
  using Bling.Matrices;
  using Bling.WPF3D;
  using Bling.DX;
  public class ParticlesGSX : Canvas, ICanStart {
    public interface VSParticleIn : ISignature {
      [Semantics.Position()]
      Point3DBl Position { get; set; }
      [Semantics.Normal()]
      Point3DBl Velocity { get; set; }
      [Semantic("TIMER")]
      DoubleBl Timer { get; set; }
      [Semantic("TYPE")]
      IntBl Kind { get; set; }
    }
    public static class PtKind {
      public static readonly int Launcher = 0; // firework launcher - launches a shell every so many seconds.
      public static readonly int Shell = 1; // unexploded shell -files from the origin and explodes into many ember1's
      public static readonly int Ember1 = 2; // basic particl, after it's emitted from a shell, it dies
      public static readonly int Ember2 = 3; // after it's emitted, it explodes again into many Ember1's
      public static readonly int Ember3 = 4; // just a differently colored ember1
    }
    public static readonly double ShellLife = 3.0;
    public static readonly double Ember1Life = 2.5;
    public static readonly double Ember2Life = 1.5;
    public static readonly double Ember3Life = 2.0;
    public static readonly int MAX_PARTICLES = 3000;

    public interface VSParticleDrawOut : ISignature {
      [Semantics.Position()]
      Point3DBl Position { get; set; }
      [Semantics.Color()]
      RGBBl Color { get; set; }
      [Semantic("RADIUS")]
      DoubleBl Radius { get; set; }
    }
    public interface PSSceneIn : IPixelInput {
      [Semantics.TexCoord()]
      PointBl Tex { get; set; }
      [Semantics.Color()]
      RGBBl Color { get; set; }
    }
    public interface Uniforms : ISignature {
      MatrixBl<D4, D4> WorldViewProjection { get; set; }
      MatrixBl<D4, D4> InvView { get; set; }
      DoubleBl GlobalTime { get; set; }
      DoubleBl ElapsedTime { get; set; }
      Point3DBl FrameGravity { get; set; }
      DoubleBl SecondsPerFirework { get; set; }
      IntBl NumEmber1s { get; set; }
      DoubleBl MaxEmber2s { get; set; }
      TextureBl Diffuse { get; set; }
      Texture1DBl Random { get; set; }
    }
    public static readonly Point3DBl[] Positions = new Point3DBl[] {
        new Point3DBl( -1, 1, 0 ),
        new Point3DBl( 1, 1, 0 ),
        new Point3DBl( -1, -1, 0 ),
        new Point3DBl( 1, -1, 0 ),
    };
    public static readonly PointBl[] TexCoords = new PointBl[] {
        new PointBl(0,1), 
        new PointBl(1,1),
        new PointBl(0,0),
        new PointBl(1,0),
    };
    public static readonly Sample.State SampleLinear = new Sample.State() {
      Filter = new Sample.Filter.Min() { MagOpt = Sample.Option.NONE, MipOpt = Sample.Option.LINEAR },
      AddressU = Sample.AddressMode.Clamp,
      AddressV = Sample.AddressMode.Clamp,
    };
    public static readonly Sample.State SamplePoint = new Sample.State() {
      Filter = new Sample.Filter.Min() { MagOpt = Sample.Option.NONE, MipOpt = Sample.Option.POINT },
      AddressU = Sample.AddressMode.Wrap,
      AddressV = Sample.AddressMode.Wrap,
    };
    public static readonly Blend.State AdditiveBlending = new Blend.State() {
      AlphaToCoverageEnable = false,
      Target0 = { Enable = true, WriteMask = (Blend.ColorWriteEnable) 0x0F },
      Core = {
        Src = new Blend.ComplexOption() {
          Source = Blend.Source.Src,
          ColorType = Blend.ColorType.Alpha
        },
        Dest = Blend.Option.One,
        Op = Blend.Operation.Add
      },
      Alpha = { Src = Blend.Option.Zero, Dest = Blend.Option.Zero, Op = Blend.Operation.Add },
    };
    public static readonly Blend.State NoBlending = new Blend.State() {
      AlphaToCoverageEnable = false, Target0 = { Enable = false },
    };
    public static readonly DepthStencil.State DisableDepth = new DepthStencil.State() {
      Depth = {
        Enable = false, WriteMask = DepthStencil.DepthWriteMask.Zero,
      },
    };
    public static readonly int RandomDim = 32;

    // These three lines are boiler plate to start and stop the rendering loop as this canvas is active/inactive.
    private readonly ICanStart Host;
    public void Start() { Host.Start(); }
    public void Stop() { Host.Stop(); }

    // the real work begins.
    public ParticlesGSX() {
      var effect = new Effect<Uniforms>();
      var VSScenemain = new Effect<Uniforms>.VertexShader<VSParticleIn, VSParticleDrawOut>(effect, (uniforms, input, output, vertex) => {
        output.Position = input.Position;
        DoubleBl[] Radius = new DoubleBl[5];
        Radius[0] = 1.0;
        Radius[1] = 1.0;
        for (int i = 2; i < Radius.Length; i++) Radius[i] = 1.5;
        RGBBl[] Colors0 = new RGBBl[] {
          new RGBBl(1.0, 0.1, 0.1),
          new RGBBl(0.1, 1.0, 1.0),
          new RGBBl(1.0, 1.0, 0.1),
          new RGBBl(1.0, 0.1, 1.0),
          new RGBBl(1.0, 0.1, 0.1),
        };
        Colors0[2] *= (input.Timer / Ember1Life);
        Colors0[4] *= (input.Timer / Ember3Life);

        output.Radius = Radius.Table(input.Kind);
        output.Color = Colors0.Table(input.Kind);
      });
      var VSPassThroughmain = new Effect<Uniforms>.VertexShader<VSParticleIn, VSParticleIn>(effect, (uniforms, input, output, vertex) => {
        output.Kind = input.Kind;
        output.Position = input.Position;
        output.Timer = input.Timer;
        output.Velocity = input.Velocity;
      });
      Func<Uniforms, DoubleBl, Point3DBl> RandomDir = (uniforms, offset) => {
        var Coord = (uniforms.GlobalTime + offset) / 300.0;
        return (Point3DBl) uniforms.Random.SampleLevel(SamplePoint, 0)[Coord].ScRGB;
      };
      Func<Uniforms, VSParticleIn, Effect.GeometryStream<VSParticleIn, Topology.PointKind>, Effect.GeometryStream<VSParticleIn, Topology.PointKind>> GSGenericHandler = (uniforms, input, stream) => {
        var output = ShaderSigs.Copy(input);
        output.Position = input.Position + input.Velocity * uniforms.ElapsedTime;
        output.Velocity = input.Velocity + uniforms.FrameGravity;
        output.Timer = input.Timer - uniforms.ElapsedTime;
        return stream.Append(output);
      };
      Func<Uniforms, VSParticleIn, Effect.GeometryStream<VSParticleIn, Topology.PointKind>, Effect.GeometryStream<VSParticleIn, Topology.PointKind>> GSLauncherHandler = (uniforms, input, stream) => {
        var generateNew = input.Timer <= 0;
        var random = RandomDir(uniforms, input.Kind).Normalize;
        {
          var output = ShaderSigs.Copy(input);
          output.Timer = generateNew.Condition(uniforms.SecondsPerFirework + random.X * 0.4, input.Timer - uniforms.ElapsedTime);
          stream = stream.Append(output);
        }
        {
          var output = ShaderSigs.Copy(input);
          output.Position = input.Position + input.Velocity * uniforms.ElapsedTime;
          output.Velocity = input.Velocity + random * 8.0;
          output.Timer = ShellLife + random.Y * 0.5;
          output.Kind = PtKind.Shell;
          stream = stream.Append(generateNew, output);
        }
        return stream;
      };
      Func<Uniforms, VSParticleIn, Effect.GeometryStream<VSParticleIn, Topology.PointKind>, Effect.GeometryStream<VSParticleIn, Topology.PointKind>> GSShellHandler = (uniforms, input, stream) => {
        Point3DBl random;
        var streamB = stream.For(uniforms.NumEmber1s, 100, (i, streamA) => {
          random = RandomDir(uniforms, input.Kind + i).Normalize;
          var output = ShaderSigs.Copy(input);
          output.Position = input.Position + input.Velocity * uniforms.ElapsedTime;
          output.Velocity = input.Velocity + random * 15d;
          output.Timer = Ember1Life;
          output.Kind = PtKind.Ember1;
          return streamA.Append(output);
        });
        random = RandomDir(uniforms, input.Kind + uniforms.NumEmber1s - 1).Normalize;
        streamB = streamB.For((IntBl)(random.X.Abs * uniforms.MaxEmber2s), 28, (i, streamA) => {
          random = RandomDir(uniforms, input.Kind + i).Normalize;
          var output = ShaderSigs.Copy(input);
          output.Position = input.Position + input.Velocity * uniforms.ElapsedTime;
          output.Velocity = input.Velocity + random * 15d;
          output.Timer = Ember2Life + 0.4 * random.X;
          output.Kind = PtKind.Ember2;
          return streamA.Append(output);
        });
        return (input.Timer <= 0).Condition(streamB, GSGenericHandler(uniforms, input, stream));
      };
      Func<Uniforms, VSParticleIn, Effect.GeometryStream<VSParticleIn, Topology.PointKind>, Effect.GeometryStream<VSParticleIn, Topology.PointKind>> GSEmber1Handler = (uniforms, input, stream) => {
        return (input.Timer > 0).Condition(GSGenericHandler(uniforms, input, stream), stream);
      };
      Func<Uniforms, VSParticleIn, Effect.GeometryStream<VSParticleIn, Topology.PointKind>, Effect.GeometryStream<VSParticleIn, Topology.PointKind>> GSEmber2Handler = (uniforms, input, stream) => {
        var streamB = stream;
        streamB = streamB.For(10, (i, streamA) => {
          var output = ShaderSigs.Copy(input);
          output.Position = input.Position + input.Velocity * uniforms.ElapsedTime;
          output.Velocity = input.Velocity + RandomDir(uniforms, input.Kind + i).Normalize * 10d;
          output.Timer = Ember3Life;
          output.Kind = PtKind.Ember3;
          return streamA.Append(output);
        });
        return (input.Timer <= 0).Condition(streamB, GSGenericHandler(uniforms, input, stream));
      };
      var GSAdvancedParticlesMain = new Effect<Uniforms>.GeometryShader<VSParticleIn, VSParticleIn, Topology.PointKind, Topology.PointKind>(
        effect, (uniforms, inputs, geom) => {
          var stream = new Effect.GeometryStream<VSParticleIn, Topology.PointKind>();
          // DEBUG
          if (false) return stream.Append(inputs(0));
          var streams = new Effect.GeometryStream<VSParticleIn, Topology.PointKind>[5];
          streams[PtKind.Launcher] = GSLauncherHandler(uniforms, inputs(0), stream);
          streams[PtKind.Shell] = GSShellHandler(uniforms, inputs(0), stream);
          streams[PtKind.Ember1] = GSEmber1Handler(uniforms, inputs(0), stream);
          streams[PtKind.Ember2] = GSEmber2Handler(uniforms, inputs(0), stream);
          streams[PtKind.Ember3] = streams[PtKind.Ember1];
          //if (true) return GSLauncherHandler(uniforms, inputs(0), stream);
          return streams.Table(inputs(0).Kind);
        });

      var GSScenemain = new Effect<Uniforms>.GeometryShader<VSParticleDrawOut, PSSceneIn, Topology.PointKind, Topology.TriangleKind>(
        effect, (uniforms, inputs, geom) => {
          var stream = new Effect.GeometryStream<PSSceneIn, Topology.TriangleKind>();
          if (false) {
            var output = ShaderSigs.New<PSSceneIn>();
            output.Position = 0d;
            output.Color = Colors.Black.Bl().ScRGB;
            output.Tex = 0d;
            return stream.Append(output);
          }


          stream = stream.For(4, (i, streamA) => {
            var output = ShaderSigs.New<PSSceneIn>();
            var position = Positions.Table(i) * inputs(0).Radius;
            output.Position = ((Point3DBl)(position * uniforms.WorldViewProjection)) + inputs(0).Position;
            output.Color = inputs(0).Color;
            output.Tex = TexCoords.Table(i);
            return streamA.Append(output);
          });
          stream = stream.RestartStrip();
          return stream;
        });

      var PSceneMain = new Effect<Uniforms>.PixelShader<PSSceneIn>(effect, (uniforms, input, pixel) => {
        pixel.Color = uniforms.Diffuse.Sample(SampleLinear)[input.Tex] * input.Color;
      });

      var RenderParticles = new Effect<Uniforms>.Technique<VSParticleIn, PSSceneIn, Topology.PointKind, Topology.TriangleKind>(effect);
      new Effect<Uniforms>.Pass<VSParticleIn, VSParticleDrawOut, PSSceneIn, Topology.PointKind, Topology.TriangleKind>(
        RenderParticles, VSScenemain, GSScenemain, PSceneMain) {
          Blend = { State = AdditiveBlending, Factor = 0d, SampleMask = 0xFFFFFFFF },
          DepthStencil = { State = DisableDepth, StencilRef = 0 },
        };

      var AdvanceParticles = new Effect<Uniforms>.Technique<VSParticleIn, Topology.PointKind>(effect);
      new Effect<Uniforms>.PassNoPS<VSParticleIn, VSParticleIn, VSParticleIn, Topology.PointKind, Topology.PointKind>(
        AdvanceParticles, VSPassThroughmain, GSAdvancedParticlesMain) {
          DepthStencil = { State = DisableDepth, StencilRef = 0 },
        };

      TextureBl ParticleTexture = new ImageTexture<D2>("Resources/particle.png".MakePackUri(typeof(ParticlesGS)));
      Texture1DBl RandomTexture = new UserTexture<D1>(1024) {
        Init = (p) => new ColorBl(((RGBBl)new Random()), new Random()),
      };
      PerspectiveCameraCl Camera = new PerspectiveCameraCl() {
        Position = new Point3DBl(0, 0, -170),
        Direction = { Look = new Point3DBl(0, 70, 0) },
        //FieldOfView = .25.PI().AsDegrees,
        //D3DXVECTOR3 vecEye( 0.0f, 0.0f, -170.0f );
        //D3DXVECTOR3 vecAt ( 0.0f, 70.0f, 0.0f );
        //g_Camera.SetViewParams( &vecEye, &vecAt );
        //g_Camera.SetRadius( 170.0f, 170.0f, 170.0f );
      };
      BaseBufferBl<VSParticleIn, Topology.PointKind> ParticleStart = new UserBuffer<VSParticleIn, Topology.PointKind, Topology.ListOrg>() {
        VertexCount = 1,
        F0 = (vid, input) => {
          input.Position = 0d;
          input.Velocity = new Point3DBl(0, 40, 0);
          input.Timer = 0d;
          input.Kind = 0;
        },
      };
      var ParticleA = new StreamOutBuffer<VSParticleIn, Topology.PointKind>() {
        MaxCount = MAX_PARTICLES,
      };
      var ParticleB = new StreamOutBuffer<VSParticleIn, Topology.PointKind>() {
        MaxCount = MAX_PARTICLES,
      };
      bool IsFirst0 = true;
      bool SwapFromTo0 = true;

      double LastTime = 0d; // in milli
      double TimeStep = 10d;


      BoolBl IsFirst = true; IsFirst = IsFirst.StatefulMap<bool, BoolBl>(b => IsFirst0);
      BoolBl SwapFromTo = true; SwapFromTo = SwapFromTo.StatefulMap<bool, BoolBl>(b => SwapFromTo0);
      var ParticleDrawFromA = SwapFromTo.Condition((BaseBufferBl<VSParticleIn, Topology.PointKind>)ParticleA, ParticleB);
      var ParticleDrawFromB = SwapFromTo.Condition((BaseBufferBl<VSParticleIn, Topology.PointKind>)ParticleB, ParticleA);
      var ParticleStreamTo = SwapFromTo.Condition((StreamOutBufferBl<VSParticleIn, Topology.PointKind>)ParticleB, ParticleA);
      var DrawFromNowA = (IsFirst).Condition(ParticleStart, ParticleDrawFromA);
      var DrawFromNowB = ParticleDrawFromA;
      var UniformsManager = new DefaultSignatureManager2();
      {
        double Div = 100000d; // 110d; // 1000d;
        DoubleBl CurrentTime = 1d.Bl().StatefulMap<double, DoubleBl>(d => {
          return (LastTime + TimeStep) / Div;
        });
        DoubleBl ElapsedTime = 1d.Bl().StatefulMap<double, DoubleBl>(d => {
          return TimeStep / Div;
        });
        var Uniforms = ShaderSigs.Generate<Uniforms>(UniformsManager);
        var UseMatrix = MatrixBl<D4, D4>.Identity;
        Uniforms.WorldViewProjection = UseMatrix; // Camera.Matrix;
        Uniforms.InvView = UseMatrix.Invert(); // Camera.View.Invert();
        Uniforms.GlobalTime = CurrentTime;
        Uniforms.ElapsedTime = ElapsedTime;
        Uniforms.Random = RandomTexture;
        Uniforms.Diffuse = ParticleTexture;
        Point3DBl vGravity = new Point3DBl(0, +9.8f, 0);
        Uniforms.FrameGravity = vGravity * ElapsedTime;
        Uniforms.SecondsPerFirework = 0.4;
        Uniforms.NumEmber1s = 100;
        Uniforms.MaxEmber2s = 15d;
      }

      var Host0 = new WPFHostBl(this, new PointBl(500, 500), Device => {
        var RenderA = new RenderEntry<Uniforms, VSParticleIn, VSParticleIn, Topology.PointKind, Topology.PointKind, Topology.ListOrg>() {
          StreamOut = ParticleStreamTo,
          TechniqueB = AdvanceParticles,
          Vertices = DrawFromNowA,
          UniformsManager = UniformsManager,
          Target = Device.Display,
        };
        var RenderB = new RenderEntry<Uniforms, VSParticleIn>() {
          Technique = RenderParticles,
          Target = Device.Display,
          Vertices = DrawFromNowB,
          UniformsManager = UniformsManager,
        };
        Device.InitRender(() => {
          IsFirst0 = false;
          SwapFromTo0 = !SwapFromTo0;
          LastTime += TimeStep;
        }, RenderA, RenderB);
      }) {
        LeftTop = 0d,
        ZIndex = 0,
      };
      // so start and stop work.
      Host = Host0.CurrentValue;


    }
  }
}
